package com.webserver.http;

import java.io.*;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 响应对象
 * 该类的每一个实例用于表示HTTP协议要求的响应内容
 * 一个响应由三部分构成
 * 状态行,响应头,响应正文
 */
public class HttpResponse {
    //状态行相关信息
    private int statusCode = 200;//默认值200
    private String statusReason = "OK";

    //响应头相关信息
    private Map<String,String> headers = new HashMap<>();

    //响应正文相关信息
    private File entity;//正文对应的实体文件

    /*
        低级流,其内部维护一个字节数组,通过这个流写出的数据实际上存到了该数组中.
     */
    private ByteArrayOutputStream baos;

    /*
        返回给外界,使得外界可以通过这个流将要发送给客户端的正文内容写入到响应对象中.
        这个流在初始化后会最终连接到上面的baos上,使得写出的文本数据转换为字节后保存在baos这个流的内部数组.
        这样一来最终在发送正文时,就可以将baos内部的字节数组内容作为正文发送了
     */
    private PrintWriter pw;

    private Socket socket;

    public HttpResponse(Socket socket) {
        this.socket = socket;
    }

    /**
     * 将当前响应对象内容以标准的响应格式发送给客户端
     */
    public void flush() throws IOException {
        //发送前的准备工作
        sendBefore();
        //1.发送状态行
        sendStatusLine();
        //2.发送响应头
        sendHeaders();
        //3.发送响应正文
        sendContent();
    }

    /**
     *  发送响应前的准备工作,该方法调用后,就开始正式发送工作.
     *  此方法是flush中的第一个操作
     */
    private void sendBefore(){
        if (baos!=null){//是否使用过pw向当前响应对象中添加了拼接的正文内容
            pw.flush();//将缓存数据写入baos内部数组
            //得到baos内部的数组(要给客户端发送的正文数据)
            byte[] data = baos.toByteArray();
            //设置响应头Content-Length
            addHeader("Content-Length",data.length+"");
            //Content-Type无法根据数组内容分析,所以无法自动设置
        }
        //后面还可以扩展其他需要在发送前准备的内容
    }

    private void sendStatusLine() throws IOException {
        String line = "HTTP/1.1"+" "+statusCode+" "+statusReason;
        println(line);
    }

    private void sendHeaders() throws IOException {
//        String line = "Content-Type: text/html";
//        println(line);
//        line = "Content-Length: " + entity.length();
//        println(line);

        //遍历headers将所有响应头发送
        Set<Map.Entry<String,String>> entrySet = headers.entrySet();
        for(Map.Entry<String,String> e : entrySet){
            String name = e.getKey();//响应头名字
            String value = e.getValue();//响应头值
            String line = name+": "+value;
            println(line);
        }

        //单独发送CRLF表示消息头部分发送完毕
        println("");
    }

    private void sendContent() throws IOException {
        if (baos!=null){
            byte[] data = baos.toByteArray();
            OutputStream out = socket.getOutputStream();
            out.write(data);//将数据作为正文发送给客户端
        }else if(entity!=null) {
            try (
                    FileInputStream fis = new FileInputStream(entity)
            ) {
                OutputStream out = socket.getOutputStream();
                int len;
                byte[] buf = new byte[1024 * 10];
                while ((len = fis.read(buf)) != -1) {
                    out.write(buf, 0, len);
                }
            }
        }
    }

    /**
     *  重定向
     *  通过这个响应,要求浏览器再次发起请求去请求指定的路径
     */
    public void sendRedirect(String uri) {
        //设置状态代码为重定向:302
        setStatusCode(302);
        setStatusReason("Moved Temporarily");
        //重定向要求一个必要的响应头:Location,用于指定浏览器重新发起请求的路径
        addHeader("Location",uri);
    }

    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes("ISO8859-1");
        out.write(data);
        out.write(13);
        out.write(10);
    }

    public File getEntity() {
        return entity;
    }

    /**
     *  向响应中添加响应正文的实体文件,该方法会同时根据文件添加对应的响应头
     *  Content-Type和Content-Length
     * @param entity
     */
    public void setEntity(File entity) {
        this.entity = entity;
        String ext = entity.getName().substring(entity.getName().lastIndexOf(",")+1);
        String Type = HttpContext.getMimeType(ext);
        addHeader("Content-Type",Type);
        addHeader("Content-Length",entity.length()+"");
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getStatusReason() {
        return statusReason;
    }

    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }

    /**
     * 添加一个要发送的响应头
     * @param name
     * @param value
     */
    public void addHeader(String name,String value){
        headers.put(name, value);
    }

    public PrintWriter getWriter(){
        if (pw==null){//pw没有被初始化过
            //初始化baos
            baos = new ByteArrayOutputStream();
            //进行流链接
            pw = new PrintWriter(
                    new BufferedWriter(
                            new OutputStreamWriter(
                                    baos
                            )
                    )
            );
        }
        return pw;
    }

    public void setContentType(String mime) {
        addHeader("Content-Type",mime);
    }
}